<?php
/**
 * Properties
 * 
 * @category	Util
 * @package		dayscript.util
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource	
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */

namespace dayscript\util;

/**
 * Properties
 *
 * General Properties implementation
 * 
 * @category	Util
 * @package		dayscript.util
 * @subpackage	
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license		
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\Map, \InvalidArgumentException, \BadMethodCallException
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-13 $
 */
class Properties extends \dayscript\Map	{
	/**
	 * Default properties values
	 *
	 * @var Map $defaults
	 */
	private $defaults = null;
	/**
	 * Where or not these properties can be changed
	 *
	 * @var boolean $readonly
	 */
	private $readonly = false;
	/**
	 * Creates an empty property list with the specified defaults.
	 *
	 * @param array|\dayscript\Map $defaults
	 * @param Boolean $readonly
	 * @return Properties
	 */
	public function __construct( $defaults = null, $readonly = false )	{
		parent::__construct( );
		if( $defaults != null )	{
			if( \is_array( $defaults ) || $defaults instanceof \dayscript\Map )	{
				foreach ( $defaults as $key => $value )	{
					if( \is_array( $value ) || $value instanceof \dayscript\Map )
						$this[$key] = new self( $value, $this->readonly );
					else
						$this[$key] = $value;
				}
			}
			else
				throw new \InvalidArgumentException( 'Parameter must be an array or Map', E_USER_ERROR );
		}
		
		$this->readonly = $readonly;
		if( $readonly )
			$this->defaults = $this;
		else
			$this->defaults = new \dayscript\Map( $defaults );
	}
	/**
	 * 
	 * @see \Serializable::serialize()
	 */
	public function serialize( )	{
		return \serialize( array( parent::serialize( ), $this->readonly, ( $this->readonly ? null : $this->defaults->serialize( ) ) ) );
	}
	/**
	 * @see \Serializable::unserialize()
	 */
	public function unserialize( $data ) {
		$data = \unserialize( $data );
		
		parent::unserialize( $data[0] );
		$this->readonly = $data[1];
		$this->defaults = ( $this->readonly ? $this : $data[2]->unserialize( ) );
	}
	/**
	 * @see ArrayAccess
	 */
	public function offsetSet( $offset, $value )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to set a value on a readonly Object', \E_USER_ERROR );
		parent::offsetSet( $offset, $value );
	}
	public function offsetUnset( $offset )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to remove a value from a readonly Object', \E_USER_ERROR );
		parent::offsetUnset( $offset );
	}
	/**
	 * Restarts all Map data.
	 *
	 */
	public function clear( )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying clear a readonly Object', \E_USER_ERROR );
		parent::clear( );
	}
	/**
	 * Associates the specified value with the specified key in this map.
	 *
	 * @param mixed $offset
	 * @param mixed $value
	 * @return mixed|null $actual Previous value associated with specified key, or null if there was no mapping for key
	 */
	public function put( $offset, $value )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to set a value on a readonly Object', \E_USER_ERROR );
		return parent::put( $offset, $value );
	}
	/**
	 * Removes the mapping for this key from this map if it is present.
	 *
	 * @param mixed $offset key whose mapping is to be removed from the map.
	 * @return mixed|null $actual Value to which the map previously associated the key, or null if the map contained no mapping for this key.
	 */
	public function remove( $offset )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to remove a value from a readonly Object', \E_USER_ERROR );
		return parent::remove( $offset );
	}
	/**
	 * Copy all information into current Map
	 *
	 * @param array|Map $data
	 */
	public function putAll( $data )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to put values into a readonly Object', \E_USER_ERROR );
		parent::putAll( $data );
	}
	/**
	 * Returns the value to which this properties maps the specified key.
	 *
	 * @param mixed $offset
	 * @param mixed $default
	 * @return mixed|$default $value The value to which this map maps the specified key, or $default if the map contains no mapping for this key.
	 */
	public function getProperty( $offset, $default = null )	{
		return parent::get( $offset, $default );
	}
	/**
	 * Associates the specified value with the specified key in this properties.
	 *
	 * @param mixed $offset
	 * @param mixed $value
	 * @return mixed|null $actual Previous value associated with specified key, or null if there was no mapping for key
	 */
	public function setProperty( $offset, $value )	{
		if( $this->readonly )
			throw new \BadMethodCallException( 'Trying to set a value on a readonly Object', \E_USER_ERROR );
		return parent::put( $offset, $value );
	}
	/**
	 * Returns an array of the names contained in this properties.
	 *
	 * @return array $names
	 */
	public function propertyNames( )	{
		return parent::keys( );
	}
	/**
	 * Restore de default values.
	 */
	public function restoreDefaultValues( )	{
		$this->clear( );
		$this->putAll( $this->defaults );
	}
	/**
	 * Where this is a readonly object 
	 * 
	 * @return boolean $readonly
	 */
	public function isReadonly( )	{
		return $this->readonly;
	}
}
